package cursor_animation;



import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;

import javax.swing.*;

import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.CaretVisualAttributes;
import com.intellij.openapi.editor.EditorSettings;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.editor.impl.view.EditorView;
import com.intellij.openapi.wm.impl.IdeBackgroundUtil;
import com.intellij.ui.JBColor;
import com.intellij.ui.scale.JBUIScale;


import java.awt.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.impl.view.EditorPainter;
import com.intellij.openapi.editor.impl.view.EditorView;

  class EditorPainterProxy {

    public static final String CLASS_NAME = EditorPainter.class.getName();
    public static class SessionProxy {

        public static final String CLASS_NAME = EditorPainterProxy.CLASS_NAME + "$Session";
        private static Class<?> SESSION_CLASS;
        private static Method PAINT_CARET_BAR;
        private static Method PAINT_CARET_BLOCK;
        private static Method PAINT_CARET_UNDERSCORE;
        private static Method PAINT_CARET_BOX;
        private static Method PAINT_CARET_TEXT;

        static {
            try {
                SESSION_CLASS = Class.forName(CLASS_NAME);
                // Session#paintCaretBar(Graphics2D, float, float, float, float, boolean)
                PAINT_CARET_BAR = SESSION_CLASS.getDeclaredMethod("paintCaretBar",
                        Graphics2D.class, Caret.class, float.class, float.class, float.class, float.class, boolean.class);
                // Session#paintCaretBlock(Graphics2D, float, float, float, float)
                PAINT_CARET_BLOCK = SESSION_CLASS.getDeclaredMethod("paintCaretBlock",
                        Graphics2D.class, float.class, float.class, float.class, float.class);
                // Session#paintCaretUnderscore(Graphics2D, float, float, float, float)
                PAINT_CARET_UNDERSCORE = SESSION_CLASS.getDeclaredMethod("paintCaretUnderscore",
                        Graphics2D.class, float.class, float.class, float.class, float.class);
                // Session#paintCaretBox(Graphics2D, float, float, float, float)
                PAINT_CARET_BOX = SESSION_CLASS.getDeclaredMethod("paintCaretBox",
                        Graphics2D.class, float.class, float.class, float.class, float.class);
                // Session#paintCaretText(Graphics2D, Caret, Color, float, float, int, boolean)
                PAINT_CARET_TEXT = SESSION_CLASS.getDeclaredMethod("paintCaretText",
                        Graphics2D.class, Caret.class, Color.class, float.class, float.class, int.class, boolean.class);

                PAINT_CARET_BAR.setAccessible(true);
                PAINT_CARET_BLOCK.setAccessible(true);
                PAINT_CARET_UNDERSCORE.setAccessible(true);
                PAINT_CARET_BOX.setAccessible(true);
                PAINT_CARET_TEXT.setAccessible(true);
            } catch (ReflectiveOperationException e) {
                e.printStackTrace();
            }
        }

        public static Object createSession(EditorView editorView, Graphics2D g) {
            try {
                Constructor<?> constructor = SESSION_CLASS.getDeclaredConstructor(EditorView.class, Graphics2D.class);
                constructor.setAccessible(true);
                return constructor.newInstance(editorView, g);
            } catch (ReflectiveOperationException e) {
                e.printStackTrace();
                return null;
            }
        }

        public static void paintCaretBar(Object session, Graphics2D g, Caret caret, float x, float y, float w, float h, boolean isRtl) {
            try {
                PAINT_CARET_BAR.invoke(session, g, caret, x, y, w, h, isRtl);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }

        public static void paintCaretBlock(Graphics2D g, float x, float y, float w, float h) {
            try {
                PAINT_CARET_BLOCK.invoke(null, g, x, y, w, h);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }

        public static void paintCaretUnderscore(Graphics2D g, float x, float y, float w, float h) {
            try {
                PAINT_CARET_UNDERSCORE.invoke(null, g, x, y, w, h);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }

        public static void paintCaretBox(Graphics2D g, float x, float y, float w, float h) {
            try {
                PAINT_CARET_BOX.invoke(null, g, x, y, w, h);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }

        public static void paintCaretText(Object session, Graphics2D g, Caret caret, Color caretColor, float x, float y, int topOverhang, boolean isRtl) {
            try {
                PAINT_CARET_TEXT.invoke(session, g, caret, caretColor, x, y, topOverhang, isRtl);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }

    }

}
 class CaretPainter extends JComponent  implements ComponentListener  {

    private final SmoothCaret smoothCaret;
    private final Caret caret;
    private final EditorImpl editor;
    private final EditorView editorView;
    private final Insets contentInsets;
    private final CaretVisualAttributes attributes;

    public CaretPainter(SmoothCaret smoothCaret, Caret caret, EditorImpl editor, EditorView editorView) {
        super();
        this.smoothCaret = smoothCaret;
        this.caret = caret;
        this.editor = editor;
        this.editorView = editorView;
        this.contentInsets = this.editor.getContentComponent().getInsets();
        this.attributes = this.caret.getVisualAttributes();
        setVisible(true);
        this.update();
    }

    @Override
    public void componentResized(ComponentEvent componentEvent) {
        this.update();
        this.repaint();
    }

    @Override
    public void componentMoved(ComponentEvent componentEvent) {
        this.update();
        this.repaint();
    }

    @Override
    public void componentShown(ComponentEvent componentEvent) {
    }

    @Override
    public void componentHidden(ComponentEvent componentEvent) {
    }

    public void update() {
        this.setBounds(editor.getScrollingModel().getVisibleArea());
    }

    @Override
    public void paintComponent(Graphics graphics) {
        super.paintComponent(graphics);
        if (editor.isPurePaintingMode())
            return;
        Graphics2D g = IdeBackgroundUtil.getOriginalGraphics(graphics);

        Object session = EditorPainterProxy.SessionProxy.createSession(editorView, g);
        int nominalLineHeight = editorView.getNominalLineHeight();
        int topOverhang = editorView.getTopOverhang();
        EditorSettings settings = editor.getSettings();
        Color caretColor = editor.getColorsScheme().getColor(EditorColors.CARET_COLOR);
        if (caretColor == null)
            caretColor = new JBColor(JBColor.DARK_GRAY, JBColor.LIGHT_GRAY);
        int minX = contentInsets.left;
        Point2D point = smoothCaret.position();
        float x = (float) point.getX();
        int yShift = -editor.getScrollingModel().getVisibleArea().y;
        int y = (int) point.getY() - topOverhang + yShift;
        g.setColor(attributes.getColor() != null ? attributes.getColor() : caretColor);
        boolean isRtl = caret.isAtRtlLocation();
        float width = caret.getVisualAttributes().getWidth(1);
        float startX = isRtl ? x - width : x;
        CaretVisualAttributes.Shape shape = attributes.getShape();
        switch (shape) {
            case DEFAULT -> {
                if (editor.isInsertMode() != settings.isBlockCursor()) {
                    int lineWidth = JBUIScale.scale(attributes.getWidth(settings.getLineCursorWidth()));
                    if (lineWidth > 1)
                        x -= 1 / JBUIScale.sysScale(g);
                    EditorPainterProxy.SessionProxy.paintCaretBar(session, g, caret, x, y, lineWidth, nominalLineHeight, isRtl);
                } else {
                    EditorPainterProxy.SessionProxy. paintCaretBlock(g, startX, y, width, nominalLineHeight);
                    EditorPainterProxy.SessionProxy.  paintCaretText(session, g, caret, caretColor, startX, y, topOverhang, isRtl);
                }
            }
            case BLOCK -> {
                EditorPainterProxy.SessionProxy.  paintCaretBlock(g, startX, y, width, nominalLineHeight);
                EditorPainterProxy.SessionProxy.  paintCaretText(session, g, caret, caretColor, startX, y, topOverhang, isRtl);
            }
            case BAR -> {
                // Don't draw if thickness is zero. This allows a plugin to "hide" carets, e.g. to visually emulate a block selection as a
                // selection rather than as multiple carets with discrete selections
                if (attributes.getThickness() > 0) {
                    int barWidth = Math.max((int) (width * attributes.getThickness()), JBUIScale.scale(settings.getLineCursorWidth()));
                    if (!isRtl && x > minX && barWidth > 1 && barWidth < (width / 2))
                        x -= 1 / JBUIScale.sysScale(g);
                    EditorPainterProxy.SessionProxy.  paintCaretBar(session, g, caret, isRtl ? x - barWidth : x, y, barWidth, nominalLineHeight, isRtl);
                    Shape savedClip = g.getClip();
                    g.setClip(new Rectangle2D.Float(isRtl ? x - barWidth : x, y, barWidth, nominalLineHeight));
                    EditorPainterProxy.SessionProxy. paintCaretText(session, g, caret, caretColor, startX, y, topOverhang, isRtl);
                    g.setClip(savedClip);
                }
            }
            case UNDERSCORE -> {
                if (attributes.getThickness() > 0) {
                    int underscoreHeight = Math.max((int) (nominalLineHeight * attributes.getThickness()), 1);
                    EditorPainterProxy.SessionProxy. paintCaretUnderscore(g, startX, y + nominalLineHeight - underscoreHeight, width, underscoreHeight);
                    Shape oldClip = g.getClip();
                    g.setClip(new Rectangle2D.Float(startX, y + nominalLineHeight - underscoreHeight, width, underscoreHeight));
                    EditorPainterProxy.SessionProxy.  paintCaretText(session, g, caret, caretColor, startX, y, topOverhang, isRtl);
                    g.setClip(oldClip);
                }
            }
            case BOX ->   EditorPainterProxy.SessionProxy. paintCaretBox(g, startX, y, width, nominalLineHeight);
        }
    }

}